From 2b4aca5c1785d32ec98eb6c88a6169bfcfe5fe7a Mon Sep 17 00:00:00 2001 From: oliskoli Date: Thu, 28 Jun 2007 22:17:24 +0000 Subject: [PATCH] Let unicsv write something. --- reference/expert-uni.txt | 87 +++++ testo | 2 + unicsv.c | 754 ++++++++++++++++++++++++++++++++++----- 3 files changed, 752 insertions(+), 91 deletions(-) create mode 100644 reference/expert-uni.txt diff --git a/reference/expert-uni.txt b/reference/expert-uni.txt new file mode 100644 index 000000000..84427e81c --- /dev/null +++ b/reference/expert-uni.txt @@ -0,0 +1,87 @@ +No,Latitude,Longitude,Name,Altitude,Description,Notes,Symbol,Date,Time +1,42.438878,-71.119277,"5066",44.6,"5066","5066","Crossing",2001/11/28,22:05:28 +2,42.439227,-71.119689,"5067",57.6,"5067","5067","Dot",2001/06/02,05:26:55 +3,42.438917,-71.116146,"5096",44.8,"5096","5096","Dot",2001/11/17,00:03:38 +4,42.443904,-71.122044,"5142",50.6,"5142","5142","Dot",2001/11/28,22:05:28 +5,42.447298,-71.121447,"5156",127.7,"5156","5156","Dot",2001/06/02,05:26:58 +6,42.454873,-71.125094,"5224",96.9,"5224","5224","Dot",2001/06/02,05:26:59 +7,42.459079,-71.124988,"5229",82.6,"5229","5229","Dot",2001/06/02,05:26:59 +8,42.456979,-71.124474,"5237",82.9,"5237","5237","Dot",2001/06/02,05:26:59 +9,42.454401,-71.120990,"5254",66.7,"5254","5254","Dot",2001/11/28,22:05:28 +10,42.451442,-71.121746,"5258",74.6,"5258","5258","Dot",2001/11/08,00:53:41 +11,42.454404,-71.120660,"5264",65.3,"5264","5264","Dot",2001/11/28,22:05:28 +12,42.457761,-71.121045,"526708",77.4,"526708","526708","Dot",2001/06/02,05:27:00 +13,42.457089,-71.120313,"526750",74.7,"526750","526750","Dot",2001/06/02,05:27:00 +14,42.456592,-71.119676,"527614",78.7,"527614","527614","Dot",2001/11/08,00:53:41 +15,42.456252,-71.119356,"527631",78.7,"527631","527631","Dot",2001/11/08,00:53:41 +16,42.458148,-71.119135,"5278",68.3,"5278","5278","Dot",2001/06/02,05:27:00 +17,42.459377,-71.117693,"5289",64.0,"5289","5289","Dot",2001/06/02,05:27:01 +18,42.464183,-71.119828,"5374FIRE",53.0,"5374FIRE","5374FIRE","Dot",2001/11/28,22:05:28 +19,42.465650,-71.119399,"5376",56.4,"5376","5376","Dot",2001/06/02,05:27:02 +20,42.439018,-71.114456,"6006",56.4,"600698","600698","Dot",2001/06/02,05:26:55 +21,42.438594,-71.114803,"6006BLUE",46.0,"6006BLUE","6006BLUE","Dot",2001/11/28,22:05:28 +22,42.436757,-71.113223,"6014MEADOW",37.6,"6014MEADOW","6014MEADOW","Dot",2001/11/28,22:05:28 +23,42.441754,-71.113220,"6029",56.4,"6029","6029","Dot",2001/06/02,05:26:55 +24,42.436243,-71.109075,"6053",50.3,"6053","6053","Dot",2001/06/02,05:27:05 +25,42.439250,-71.107500,"6066",25.6,"6066","6066","Dot",2001/06/02,05:26:57 +26,42.439764,-71.107582,"6067",34.4,"6067","6067","Dot",2001/06/02,05:26:57 +27,42.434766,-71.105874,"6071",30.5,"6071","6071","Dot",2001/06/02,05:26:57 +28,42.433304,-71.106599,"6073",15.2,"6073","6073","Dot",2001/06/02,05:26:56 +29,42.437338,-71.104772,"6084",37.8,"6084","6084","Dot",2001/06/02,05:26:57 +30,42.442196,-71.110975,"6130",64.0,"6130","6130","Dot",2001/06/02,05:26:55 +31,42.442981,-71.111441,"6131",64.0,"6131","6131","Dot",2001/06/02,05:26:58 +32,42.444773,-71.108882,"6153",62.8,"6153","6153","Dot",2001/06/02,05:27:05 +33,42.443592,-71.106301,"6171",55.5,"6171","6171","Dot",2001/06/02,05:27:05 +34,42.447804,-71.106624,"6176",62.5,"6176","6176","Dot",2001/06/02,05:27:04 +35,42.448448,-71.106158,"6177",62.2,"6177","6177","Dot",2001/06/02,05:27:04 +36,42.453415,-71.106783,"6272",69.8,"6272","6272","Dot",2001/06/02,05:26:55 +37,42.453434,-71.107253,"6272",73.2,"6272","6272","Dot",2001/06/02,05:26:56 +38,42.458298,-71.106771,"6278",70.1,"6278","6278","Dot",2001/06/02,05:27:04 +39,42.451430,-71.105413,"6280",57.6,"6280","6280","Dot",2001/11/17,00:03:38 +40,42.453845,-71.105206,"6283",66.7,"6283","6283","Dot",2001/11/17,00:03:38 +41,42.459986,-71.106170,"6289",72.9,"6289","6289","Dot",2001/11/17,00:03:38 +42,42.457616,-71.105116,"6297",72.8,"6297","6297","Dot",2001/06/02,05:27:04 +43,42.467110,-71.113574,"6328",53.6,"6328","6328","Dot",2001/06/02,05:27:02 +44,42.464202,-71.109863,"6354",43.9,"6354","6354","Dot",2001/06/02,05:27:03 +45,42.466459,-71.110067,"635722",48.8,"635722","635722","Dot",2001/06/02,05:27:02 +46,42.466557,-71.109410,"635783",49.1,"635783","635783","Dot",2001/06/02,05:27:02 +47,42.463495,-71.107117,"6373",62.5,"6373","6373","Dot",2001/06/02,05:27:03 +48,42.401051,-71.110241,"6634",4.0,"6634","6634","Dot",2001/06/02,05:26:56 +49,42.432621,-71.106532,"6979",13.4,"6979","6979","Dot",2001/06/02,05:26:56 +50,42.431033,-71.107883,"6997",34.0,"6997","6997","Dot",2001/11/17,00:03:38 +51,42.465687,-71.107360,"BEAR HILL",87.8,"BEAR HILL TOWER","Bear Hill Tower","Tall Tower",2001/06/02,05:27:03 +52,42.430950,-71.107628,"BELLEVUE",23.5,"BELLEVUE","Bellevue Parking Lot","Parking Area",2001/06/02,02:18:15 +53,42.438666,-71.114079,"6016",43.4,"Bike Loop Connector","Bike Loop Connector","Waypoint",2001/11/28,22:05:28 +54,42.456469,-71.124651,"5236BRIDGE",89.9,"Bridge","Bridge","Bridge",2001/06/02,05:26:59 +55,42.465759,-71.119815,"5376BRIDGE",55.5,"Bridge","Bridge","Bridge",2001/06/02,05:27:01 +56,42.442993,-71.105878,"6181CROSS",52.7,"Crossing","Crossing","Crossing",2001/06/02,05:27:05 +57,42.435472,-71.109664,"6042CROSS",45.1,"Crossing","Crossing","Crossing",2001/06/02,05:27:05 +58,42.458516,-71.103646,"DARKHOLLPO",,"Dark Hollow Pond","Dark Hollow Pond","Fishing Area",, +59,42.443109,-71.112675,"6121DEAD",56.1,"Dead End","Dead End","Danger Area",2001/06/02,05:26:57 +60,42.449866,-71.119298,"5179DEAD",117.0,"Dead End","Dead End","Danger Area",2001/06/02,05:26:59 +61,42.459629,-71.116524,"5299DEAD",69.5,"Dead End","Dead End","Danger Area",2001/06/02,05:27:01 +62,42.465485,-71.119148,"5376DEAD",57.0,"Dead End","Dead End","Danger Area",2001/06/02,05:27:02 +63,42.462776,-71.109986,"6353DEAD",46.9,"Dead End","Dead End","Danger Area",2001/06/02,05:27:03 +64,42.446793,-71.108784,"6155DEAD",61.3,"Dead End","Dead End","Danger Area",2001/06/02,05:27:04 +65,42.451204,-71.126602,"GATE14",110.9,"Gate 14","Gate 14","Truck Stop",2001/06/02,05:26:59 +66,42.458499,-71.122078,"GATE16",77.7,"Gate 16","Gate 16","Truck Stop",2001/06/02,05:27:00 +67,42.459376,-71.119238,"GATE17",65.8,"Gate 17","Gate 17","Truck Stop",2001/06/02,05:27:01 +68,42.466353,-71.119240,"GATE19",57.3,"Gate 19","Gate 19","Truck Stop",2001/06/02,05:27:02 +69,42.468655,-71.107697,"GATE21",49.4,"Gate 21","Gate 21","Truck Stop",2001/06/02,05:27:03 +70,42.456718,-71.102973,"GATE24",81.1,"Gate 24","Gate 24","Truck Stop",2001/06/02,05:27:03 +71,42.430847,-71.107690,"GATE5",21.5,"Gate 5","Gate 5","Truck Stop",2001/11/28,22:05:28 +72,42.431240,-71.109236,"GATE6",26.6,"Gate 6","Gate 6","Waypoint",2001/11/08,00:53:41 +73,42.439502,-71.106556,"6077LOGS",32.0,"Log Crossing","Log Crossing","Amusement Park",2001/06/02,02:18:16 +74,42.449765,-71.122320,"5148NANEPA",119.8,"Nanepashemet Road Crossing","Nanepashemet Road Crossing","Waypoint",2001/11/08,00:53:41 +75,42.457388,-71.119845,"5267OBSTAC",73.8,"Obstacle","Obstacle","Amusement Park",2001/06/02,05:27:00 +76,42.434980,-71.109942,"PANTHRCAVE",45.3,"Panther Cave","Panther Cave","Tunnel",2001/11/08,00:53:41 +77,42.453256,-71.121211,"5252PURPLE",78.0,"Purple Rock Hill","Purple Rock Hill","Summit",2001/11/08,00:53:41 +78,42.457734,-71.117481,"5287WATER",68.0,"Reservoir","Reservoir","Swimming Area",2001/06/02,05:27:01 +79,42.459278,-71.124574,"5239ROAD",81.1,"Road","Road","Truck Stop",2001/06/02,05:27:00 +80,42.458782,-71.118991,"5278ROAD",67.4,"Road","Road","Truck Stop",2001/06/02,05:27:01 +81,42.439993,-71.120925,"5058ROAD",53.9,"ROAD CROSSING","Road Crossing","Dot",2001/06/02,02:18:14 +82,42.453415,-71.106782,"SHEEPFOLD",69.8,"Sheepfold Parking Lot","Sheepfold Parking Lot","Parking Area",2001/06/02,02:18:13 +83,42.455956,-71.107483,"SOAPBOX",64.0,"Soap Box Derby Track","Soap Box Derby Track","Cemetery",2001/06/02,05:27:04 +84,42.465913,-71.119328,"5376STREAM",64.5,"Stream Crossing","Stream Crossing","Bridge",2001/11/08,00:53:41 +85,42.445359,-71.122845,"5144SUMMIT",61.6,"Summit","Summit","Summit",2001/11/28,22:05:28 +86,42.441727,-71.121676,"5150TANK",67.4,"WATER TANK","Water Tank","Museum",2001/06/02,02:18:16 diff --git a/testo b/testo index a56eae4dd..ceb82e161 100755 --- a/testo +++ b/testo @@ -1033,6 +1033,8 @@ echo "lat,lon,descr,name,notes,unk,unk" > ${TMPDIR}/unicsv.txt cat ${REFERENCE}/mxf.mxf >> ${TMPDIR}/unicsv.txt ${PNAME} -i unicsv -f ${TMPDIR}/unicsv.txt -o gpx -F ${TMPDIR}/unicsv.gpx compare ${TMPDIR}/unicsv.gpx ${REFERENCE}/unicsv.gpx +${PNAME} -i gpx -f ${REFERENCE}/expertgps.gpx -o unicsv -F ${TMPDIR}/expert-uni.txt +compare ${TMPDIR}/expert-uni.txt ${REFERENCE}/expert-uni.txt # # Basic NMEA tests diff --git a/unicsv.c b/unicsv.c index 42602a114..2192bd2ce 100644 --- a/unicsv.c +++ b/unicsv.c @@ -1,7 +1,8 @@ /* Universal CSV - support for csv files, divining field order from the header. - Copyright (C) 2006 Robert Lipe, robertlipe@usa.net + Copyright (C) 2006 Robert Lipe, robertlipe@usa.net, + copyright (C) 2007 Olaf Klein, o.b.klein@gpsbabel.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,13 +20,23 @@ */ #include "defs.h" +#include "cet.h" +#include "cet_util.h" #include "csv_util.h" +#include "garmin_tables.h" #include "jeeps/gpsmath.h" #include "strptime.h" #include +#include #define MYNAME "unicsv" +/* "UNICSV_FIELD_SEP" and "UNICSV_LINE_SEP" are only used by the writer */ +#define UNICSV_FIELD_SEP "," +#define UNICSV_LINE_SEP "\r\n" +#define UNICSV_QUOT_CHAR '"' + +/* GPSBabel internal and calculated fields */ typedef enum { fld_shortname = 0, fld_latitude, @@ -35,7 +46,7 @@ typedef enum { fld_url, fld_altitude, fld_utm_zone, - fld_utm_char, + fld_utm_zone_char, fld_utm_northing, fld_utm_easting, fld_utm, @@ -58,6 +69,11 @@ typedef enum { fld_cadence, fld_proximity, fld_depth, + fld_symbol, + fld_date, + fld_time, + fld_datetime, + fld_iso_time, fld_terminator } field_e; @@ -71,35 +87,44 @@ typedef struct { char *name; field_e type; gbuint32 options; - } field_t; /* - * ! Use always underscores in field names ! + * ! Please use always underscores in field names ! * we check a second time after replacing underscores with spaces */ static field_t fields_def[] = { { "name", fld_shortname, STR_ANY }, { "desc", fld_description, STR_ANY }, { "notes", fld_notes, STR_ANY }, - { "comment", fld_notes, STR_ANY }, + { "omment", fld_notes, STR_ANY }, { "text", fld_notes, STR_ANY }, { "url", fld_url, STR_ANY }, + { "icon", fld_symbol, STR_ANY }, + { "symb", fld_symbol, STR_ANY }, { "lat", fld_latitude, STR_ANY }, { "lon", fld_longitude, STR_ANY }, { "x", fld_longitude, STR_EQUAL }, { "y", fld_latitude, STR_EQUAL }, + { "z", fld_altitude, STR_EQUAL }, + { "x_pos", fld_longitude, STR_ANY }, + { "y_pos", fld_latitude, STR_ANY }, { "alt", fld_altitude, STR_ANY }, { "ele", fld_altitude, STR_ANY }, { "utm_z", fld_utm_zone, STR_ANY }, - { "utm_c", fld_utm_char, STR_ANY }, + { "utm_c", fld_utm_zone_char, STR_ANY }, + { "utm_zc", fld_utm_zone_char, STR_ANY }, { "utm_n", fld_utm_northing, STR_ANY }, { "utm_e", fld_utm_easting, STR_ANY }, { "utm", fld_utm, STR_EQUAL }, + { "utm_coo", fld_utm, STR_ANY }, + { "utm_pos", fld_utm, STR_ANY }, { "bng_z", fld_bng_zone, STR_ANY }, { "bng_n", fld_bng_northing, STR_ANY }, { "bng_e", fld_bng_easting, STR_ANY }, { "bng", fld_bng, STR_EQUAL }, + { "bng_coo", fld_bng, STR_ANY }, + { "bng_pos", fld_bng, STR_ANY }, { "hdop", fld_hdop, STR_ANY }, { "pdop", fld_pdop, STR_ANY }, { "vdop", fld_vdop, STR_ANY }, @@ -108,14 +133,20 @@ static field_t fields_def[] = { { "utc_d", fld_utc_date, STR_ANY }, { "utc_t", fld_utc_time, STR_ANY }, { "head", fld_course, STR_ANY }, - { "course", fld_course, STR_ANY }, + { "cour", fld_course, STR_ANY }, { "speed", fld_speed, STR_ANY }, + { "geschw", fld_speed, STR_ANY }, /* speed in german */ { "tempf", fld_temperature_f, STR_EQUAL }, /* degrees fahrenheit */ { "temp", fld_temperature, STR_ANY }, /* degrees celsius by default */ { "heart", fld_heartrate, STR_ANY }, - { "cadence", fld_cadence, STR_ANY }, + { "caden", fld_cadence, STR_ANY }, { "prox", fld_proximity, STR_ANY }, { "depth", fld_depth, STR_ANY }, + { "date", fld_date, STR_ANY }, + { "time", fld_time, STR_ANY }, + /* unhandled columns */ + { "index", fld_terminator, STR_ANY }, + { "no", fld_terminator, STR_EQUAL }, { NULL, fld_terminator, 0 } }; @@ -123,11 +154,22 @@ static field_e *unicsv_fields_tab; static int unicsv_fields_tab_ct; static double unicsv_altscale; static char *unicsv_fieldsep; -static gbfile *file_in; +static gbfile *fin, *fout; static gpsdata_type unicsv_data_type; -static route_head *unicsv_track; +static route_head *unicsv_track, *unicsv_route; +static unsigned long long unicsv_outp_flags; +static grid_type unicsv_grid_idx; +static int unicsv_datum_idx; +static char *opt_datum, *opt_grid; +static int unicsv_waypt_ct; +static char unicsv_detect; -static arglist_t unicsv_args[] = { ARG_TERMINATOR }; +static arglist_t unicsv_args[] = { + {"datum", &opt_datum, "GPS datum (def. WGS 84)", + "WGS 84", ARGTYPE_STRING, ARG_NOMINMAX}, + {"grid", &opt_grid, "Write position using this grid.", + NULL, ARGTYPE_STRING, ARG_NOMINMAX}, + ARG_TERMINATOR }; /* helpers */ @@ -135,6 +177,73 @@ static arglist_t unicsv_args[] = { ARG_TERMINATOR }; // #define UNICSV_IS(f) (0 == strcmp(s, f)) #define UNICSV_CONTAINS(f) (0 != strstr(s, f)) +/* here we only need a simple yes(0) or no(1) */ +static int +unicsv_strrcmp(const char *s1, const char *s2) +{ + int l1, l2; + + l1 = strlen(s1); + l2 = strlen(s2); + if ((l1 - l2) >= 0) + return strcmp(s1 + (l1 - l2), s2); + else + return 1; /* false */ +} + +static int +unicsv_parse_date(const char *str) +{ + int p1, p2, p3, ct; + char sep[2]; + struct tm tm; + + memset(&tm, 0, sizeof(tm)); + ct = sscanf(str, "%d%1[-.//]%d%1[-.//]%d", &p1, sep, &p2, sep, &p3); + is_fatal(ct != 5, MYNAME ": Could not parse date string (%s).", str); + + if ((p1 > 99) || (sep[0] == '-')) { /* Y-M-D (iso like) */ + tm.tm_year = p1; + tm.tm_mon = p2; + tm.tm_mday = p3; + } + else if (sep[0] == '.') { /* Germany any other countries */ + tm.tm_mday = p1; /* have fixed D.M.Y format */ + tm.tm_mon = p2; + tm.tm_year = p3; + } + else { + tm.tm_mday = p2; + tm.tm_mon = p1; + tm.tm_year = p3; + } + if ((p1 < 100) && (p2 < 100) && (p3 < 100)) { + if (tm.tm_year < 70) tm.tm_year += 2000; + else tm.tm_year += 1900; + } + /* some low-level checks */ + if ((tm.tm_mon > 12) || (tm.tm_mon < 1) || (tm.tm_mday > 31) || (tm.tm_mday < 1)) + fatal(MYNAME ": Could not parse date string (%s).\n", str); + + tm.tm_year -= 1900; + tm.tm_mon -= 1; + + return mkgmtime(&tm); +} + +static int +unicsv_parse_time(const char *str, int *msec) +{ + int hour, min, ct; + double sec; + char sep[2]; + + ct = sscanf(str, "%d%1[.://]%d%1[.://]%lf", &hour, sep, &min, sep, &sec); + is_fatal(ct != 5, MYNAME ": Could not parse time string (%s).\n", str); + *msec = (sec - (int)sec) * 1000000; + return ((hour * SECONDS_PER_HOUR) + (min * 60) + (int)sec); +} + static char unicsv_compare_fields(char *s, const field_t *f) { @@ -154,27 +263,30 @@ unicsv_compare_fields(char *s, const field_t *f) result = (strstr(test, name) != NULL); } else { - int len = strlen(name); - if (f->options & STR_LEFT) { - result = (strncmp(test, name, len) == 0); + result = (strncmp(test, name, strlen(name)) == 0); } -/* else if (f->options & STR_RIGHT) { - result = (strrncmp(test, name, len) == 0); + result = (unicsv_strrcmp(test, name) == 0); } -*/ else { - result = 0; /* what should we do here ? */ + result = 0; /* fallback to "FALSE" */ } } if ((! result) && (strchr(test, ' ') != NULL)) { + /* replace ' ' with '_' and try again */ char *tmp = gstrsub(test, " ", "_"); result = unicsv_compare_fields(tmp, f); xfree(tmp); } - + if ((! result) && (strchr(test, '-') != NULL)) { + /* replace '-' with '_' and try again */ + char *tmp = gstrsub(test, "-", "_"); + result = unicsv_compare_fields(tmp, f); + xfree(tmp); + } + if (name != f->name) { xfree(name); xfree(test); @@ -188,7 +300,9 @@ static void unicsv_fondle_header(char *ibuf) { char *s; + char *buf = NULL; int i, column; + const cet_cs_vec_t *ascii = &cet_cs_vec_ansi_x3_4_1968; /* us-ascii */ /* Convert the entire header to lower case for convenience. * If we see a tab in that header, we decree it to be tabsep. @@ -201,13 +315,26 @@ unicsv_fondle_header(char *ibuf) else if (*s == ';') { unicsv_fieldsep = ";"; } + else if (*s == '|') { + unicsv_fieldsep = "|"; + } else { - *s = tolower(*s); + continue; } + break; + } + for (s = ibuf; *s; s++) { + *s = tolower(*s); + } + + /* convert the header line into native ascii */ + if (global_opts.charset != ascii) { + buf = cet_str_any_to_any(ibuf, global_opts.charset, ascii); + ibuf = buf; } column = -1; - while ((s = csv_lineparse(ibuf, unicsv_fieldsep, "", 0))) { + while ((s = csv_lineparse(ibuf, unicsv_fieldsep, "\"", 0))) { field_t *f = &fields_def[0]; @@ -230,6 +357,8 @@ unicsv_fondle_header(char *ibuf) } f++; } + if ((! f->name) && global_opts.debug_level) + warning(MYNAME ": Unhandled column \"%s\".\n", s); /* handle some special items */ if (f->type == fld_altitude) { @@ -237,8 +366,12 @@ unicsv_fondle_header(char *ibuf) unicsv_altscale = FEET_TO_METERS(1); } } -/* todo: date, time, maybe a few others */ + if ((f->type == fld_time) || (f->type == fld_date)) { + if (UNICSV_CONTAINS("iso")) + f->type = fld_iso_time; + } } + if (buf) xfree(buf); } static void @@ -249,12 +382,15 @@ unicsv_rd_init(const char *fname) unicsv_fields_tab = NULL; unicsv_fields_tab_ct = 0; - unicsv_data_type = wptdata; - unicsv_track = NULL; + unicsv_data_type = global_opts.objective; + unicsv_detect = (! (global_opts.masked_objective & (WPTDATAMASK | TRKDATAMASK | RTEDATAMASK | POSNDATAMASK))); - file_in = gbfopen(fname, "rb", MYNAME); + unicsv_track = unicsv_route = NULL; + unicsv_datum_idx = gt_lookup_datum_index(opt_datum, MYNAME); - if ((c = gbfgetstr(file_in))) + fin = gbfopen(fname, "rb", MYNAME); + + if ((c = gbfgetstr(fin))) unicsv_fondle_header(c); else unicsv_fieldsep = NULL; @@ -263,7 +399,7 @@ unicsv_rd_init(const char *fname) static void unicsv_rd_deinit(void) { - gbfclose(file_in); + gbfclose(fin); if (unicsv_fields_tab) xfree(unicsv_fields_tab); } @@ -273,27 +409,45 @@ unicsv_parse_one_line(char *ibuf) char *s; waypoint *wpt = NULL; int column; - int utmz = -9999; - double utme = 0; - double utmn = 0; - char utmc = 'N'; + int utm_zone = -9999; + double utm_easting = 0; + double utm_northing = 0; + char utm_zc = 'N'; char bng_zone[3] = ""; double bng_easting = 0; double bng_northing = 0; - struct tm tm; - char *ok; int checked = 0; + int date = -1, time = -1, msec = -1; + char is_localtime = 0; wpt = waypt_new(); + wpt->latitude = -9999; + wpt->longitude = -9999; + column = -1; while ((s = csv_lineparse(ibuf, unicsv_fieldsep, "\"", 0))) { - if (column > unicsv_fields_tab_ct) break; /* extra fields on line */ + if (column > unicsv_fields_tab_ct) break; /* ignore extra fields on line */ ibuf = NULL; + column++; checked++; + s = lrtrim(s); + if (! *s) continue; /* skip empty columns */ + + switch(unicsv_fields_tab[column]) { + + case fld_time: + case fld_date: + /* switch column type if it looks like an iso time string */ + if (strchr(s, 'T')) + unicsv_fields_tab[column] = fld_iso_time; + break; + default: ; + } + switch(unicsv_fields_tab[column]) { @@ -306,19 +460,19 @@ unicsv_parse_one_line(char *ibuf) break; case fld_shortname: - if (*s) wpt->shortname = xstrdup(s); + wpt->shortname = xstrdup(s); break; case fld_description: - if (*s) wpt->description = xstrdup(s); + wpt->description = xstrdup(s); break; case fld_notes: - if (*s) wpt->notes = xstrdup(s); + wpt->notes = xstrdup(s); break; case fld_url: - if (*s) wpt->url = xstrdup(s); + wpt->url = xstrdup(s); break; case fld_altitude: @@ -326,19 +480,19 @@ unicsv_parse_one_line(char *ibuf) break; case fld_utm_zone: - utmz = atoi(s); + utm_zone = atoi(s); break; case fld_utm_easting: - utme = atof(s); + utm_easting = atof(s); break; case fld_utm_northing: - utmn = atof(s); + utm_northing = atof(s); break; - case fld_utm_char: - utmc = toupper(s[0]); + case fld_utm_zone_char: + utm_zc = toupper(s[0]); break; case fld_utm: @@ -366,26 +520,26 @@ unicsv_parse_one_line(char *ibuf) case fld_hdop: wpt->hdop = atof(s); - unicsv_data_type = trkdata; + if (unicsv_detect) unicsv_data_type = trkdata; break; case fld_pdop: wpt->pdop = atof(s); - unicsv_data_type = trkdata; + if (unicsv_detect) unicsv_data_type = trkdata; break; case fld_vdop: wpt->vdop = atof(s); - unicsv_data_type = trkdata; + if (unicsv_detect) unicsv_data_type = trkdata; break; case fld_sat: wpt->sat = atoi(s); - unicsv_data_type = trkdata; + if (unicsv_detect) unicsv_data_type = trkdata; break; case fld_fix: - unicsv_data_type = trkdata; + if (unicsv_detect) unicsv_data_type = trkdata; if (case_ignore_strcmp(s, "none") == 0) wpt->fix = fix_none; else if (case_ignore_strcmp(s, "2d") == 0) @@ -400,37 +554,27 @@ unicsv_parse_one_line(char *ibuf) break; case fld_utc_date: - memset(&tm, 0, sizeof(tm)); - if (strchr(s, '.')) - ok = strptime(s, "%d.%m.%Y", &tm); - else - ok = strptime(s, "%d/%m/%Y", &tm); - if (ok) { - tm.tm_isdst = -1; - wpt->creation_time += mkgmtime(&tm); + if ((is_localtime < 2) && (date < 0)) { + date = unicsv_parse_date(s); + is_localtime = 0; } - else - fatal(MYNAME ": Could not convert date string (%s).\n", s); break; - + case fld_utc_time: - memset(&tm, 0, sizeof(tm)); - if (strptime( s, "%H:%M:%S", &tm)) { - wpt->creation_time += ( - (SECONDS_PER_HOUR * tm.tm_hour) + - (60L * tm.tm_min) + tm.tm_sec - ); + if ((is_localtime < 2) && (time < 0)) { + time = unicsv_parse_time(s, &msec); + is_localtime = 0; } break; case fld_speed: WAYPT_SET(wpt, speed, atof(s)); - unicsv_data_type = trkdata; + if (unicsv_detect) unicsv_data_type = trkdata; break; case fld_course: WAYPT_SET(wpt, course, atof(s)); - unicsv_data_type = trkdata; + if (unicsv_detect) unicsv_data_type = trkdata; break; case fld_temperature: @@ -443,12 +587,12 @@ unicsv_parse_one_line(char *ibuf) case fld_heartrate: wpt->heartrate = atoi(s); - unicsv_data_type = trkdata; + if (unicsv_detect) unicsv_data_type = trkdata; break; case fld_cadence: wpt->cadence = atoi(s); - unicsv_data_type = trkdata; + if (unicsv_detect) unicsv_data_type = trkdata; break; case fld_proximity: @@ -459,6 +603,34 @@ unicsv_parse_one_line(char *ibuf) WAYPT_SET(wpt, depth, atof(s)); break; + case fld_symbol: + wpt->icon_descr = xstrdup(s); + wpt->wpt_flags.icon_descr_is_dynamic = 1; + break; + + case fld_iso_time: + is_localtime = 2; /* fix result */ + wpt->creation_time = xml_parse_time(s, &wpt->microseconds); + break; + + case fld_time: + if ((is_localtime < 2) && (time < 0)) { + time = unicsv_parse_time(s, &msec); + is_localtime = 1; + } + break; + + case fld_date: + if ((is_localtime < 2) && (date < 0)) { + date = unicsv_parse_date(s); + is_localtime = 1; + } + break; + + case fld_datetime: + /* not implemented */ + break; + case fld_terminator: /* dummy */ checked--; break; @@ -470,29 +642,58 @@ unicsv_parse_one_line(char *ibuf) return; } - if (utmz != -9999) { - GPS_Math_UTM_EN_To_Known_Datum(&wpt->latitude, &wpt->longitude, - utme, utmn, utmz, utmc, DATUM_WGS84); + if (is_localtime < 2) { /* not fixed */ + if ((time >= 0) && (date >= 0)) { + time_t t = date + time; + + if (is_localtime) { + struct tm tm; + tm = *gmtime(&t); + wpt->creation_time = mklocaltime(&tm); + } + else + wpt->creation_time = t; + } + else if (time >= 0) + wpt->creation_time = time; + else if (date >= 0) + wpt->creation_time = date; + if (msec >= 0) + wpt->microseconds = msec; } - - if (bng_zone[0]) { - if (! GPS_Math_UKOSMap_To_WGS84_M( + + /* utm/bng can be optional */ + if ((wpt->latitude == -9999) && (wpt->longitude == -9999)) { + if (utm_zone != -9999) { + GPS_Math_UTM_EN_To_Known_Datum(&wpt->latitude, &wpt->longitude, + utm_easting, utm_northing, utm_zone, utm_zc, DATUM_WGS84); + } + else if (bng_zone[0]) { + if (! GPS_Math_UKOSMap_To_WGS84_M( bng_zone, bng_easting, bng_northing, &wpt->latitude, &wpt->longitude)) fatal(MYNAME ": Unable to convert BNG coordinates (%s %.f %.f)!\n", bng_zone, bng_easting, bng_northing); + } } switch(unicsv_data_type) { - case trkdata: - if (! unicsv_track) { - unicsv_track = route_head_alloc(); - track_add_head(unicsv_track); - } - track_add_wpt(unicsv_track, wpt); - break; - default: - waypt_add(wpt); + case rtedata: + if (! unicsv_route) { + unicsv_route = route_head_alloc(); + route_add_head(unicsv_route); + } + route_add_wpt(unicsv_route, wpt); + break; + case trkdata: + if (! unicsv_track) { + unicsv_track = route_head_alloc(); + track_add_head(unicsv_track); + } + track_add_wpt(unicsv_track, wpt); + break; + default: + waypt_add(wpt); } } @@ -503,10 +704,381 @@ unicsv_rd(void) if (unicsv_fieldsep == NULL) return; - while ((buff = gbfgetstr(file_in))) { + while ((buff = gbfgetstr(fin))) { buff = lrtrim(buff); - if (*buff) - unicsv_parse_one_line(buff); + if ((*buff == '\0') || (*buff == '#')) continue; + unicsv_parse_one_line(buff); + } +} + +/* =========================================================================== */ + +static char * +strenquote(const char *str, const char quot_char) +{ + int len; + char *cin, *cout; + char *tmp; + + if (str == NULL) cin = ""; + else cin = (char *)str; + + len = strlen(cin); + cout = tmp = xmalloc((len * 2) + 3); + + *cout++ = quot_char; + while (*cin) { + *cout++ = *cin; + if (*cin++ == quot_char) + *cout++ = quot_char; + } + *cout++ = quot_char; + *cout = '\0'; + + cout = xstrdup(tmp); + xfree(tmp); + return cout; +} + +static void +unicsv_print_str(const char *str) +{ + if (str && *str) { + char *cout; + + cout = strenquote(str, UNICSV_QUOT_CHAR); + gbfprintf(fout, "%s%s", unicsv_fieldsep, cout); + xfree(cout); + } + else gbfputs(unicsv_fieldsep, fout); +} + +#define BIT_OF(a) (1ULL << a) +#define FIELD_USED(a) (unicsv_outp_flags & (1ULL << a)) + +static void +unicsv_waypt_enum_cb(const waypoint *wpt) +{ + char *shortname = (wpt->shortname) ? wpt->shortname : ""; + + if (*shortname) unicsv_outp_flags |= BIT_OF(fld_shortname); + if (wpt->altitude != unknown_alt) unicsv_outp_flags |= BIT_OF(fld_altitude); + if (wpt->icon_descr && *wpt->icon_descr) unicsv_outp_flags |= BIT_OF(fld_symbol); + if (wpt->description && *wpt->description && (strcmp(shortname, wpt->description) != 0)) + unicsv_outp_flags |= BIT_OF(fld_description); + if (wpt->notes && *wpt->notes && (strcmp(shortname, wpt->notes) != 0)) { + if ((! wpt->description) || (strcmp(wpt->description, wpt->notes) != 0)) + unicsv_outp_flags |= BIT_OF(fld_notes); + } + if (wpt->url && *wpt->url) unicsv_outp_flags |= BIT_OF(fld_url); + if (wpt->creation_time != 0) { + unicsv_outp_flags |= BIT_OF(fld_time); + if (wpt->creation_time >= SECONDS_PER_DAY) + unicsv_outp_flags |= BIT_OF(fld_date); + } + + if (wpt->vdop > 0) unicsv_outp_flags |= BIT_OF(fld_vdop); + if (wpt->hdop > 0) unicsv_outp_flags |= BIT_OF(fld_hdop); + if (wpt->pdop > 0) unicsv_outp_flags |= BIT_OF(fld_pdop); + if (wpt->sat > 0) unicsv_outp_flags |= BIT_OF(fld_sat); + if (wpt->heartrate != 0) unicsv_outp_flags |= BIT_OF(fld_heartrate); + if (wpt->cadence != 0) unicsv_outp_flags |= BIT_OF(fld_cadence); + + /* "flagged" waypoint members */ + if WAYPT_HAS(wpt, course) unicsv_outp_flags |= BIT_OF(fld_course); + if WAYPT_HAS(wpt, depth) unicsv_outp_flags |= BIT_OF(fld_depth); + if WAYPT_HAS(wpt, speed) unicsv_outp_flags |= BIT_OF(fld_speed); + if WAYPT_HAS(wpt, proximity) unicsv_outp_flags |= BIT_OF(fld_proximity); + if WAYPT_HAS(wpt, temperature) unicsv_outp_flags |= BIT_OF(fld_temperature); +} + +static void +unicsv_waypt_disp_cb(const waypoint *wpt) +{ + double lat, lon, alt; + char *cout; + char *shortname = (wpt->shortname) ? wpt->shortname : ""; + + unicsv_waypt_ct++; + + if (unicsv_datum_idx == DATUM_WGS84) { + lat = wpt->latitude; + lon = wpt->longitude; + alt = wpt->altitude; + } + else { + GPS_Math_WGS84_To_Known_Datum_M(wpt->latitude, wpt->longitude, 0.0, + &lat, &lon, &alt, unicsv_datum_idx); + } + + gbfprintf(fout, "%d%s", unicsv_waypt_ct, unicsv_fieldsep); + + switch(unicsv_grid_idx) { + + case grid_lat_lon_ddd: + cout = pretty_deg_format(lat, lon, 'd', unicsv_fieldsep, 0); + gbfputs(cout, fout); + xfree(cout); + break; + + case grid_lat_lon_dmm: + cout = pretty_deg_format(lat, lon, 'm', unicsv_fieldsep, 0); + gbfputs(cout, fout); + xfree(cout); + break; + + case grid_lat_lon_dms: + cout = pretty_deg_format(lat, lon, 's', unicsv_fieldsep, 0); + gbfputs(cout, fout); + xfree(cout); + break; + + case grid_bng: { + char map[3]; + double north, east; + + if (! GPS_Math_WGS84_To_UKOSMap_M(wpt->latitude, wpt->longitude, &east, &north, map)) + fatal(MYNAME ": Some (or all?) of the coordinates cannot be displayed using \"BNG\".\n"); + gbfprintf(fout, "%s%s%5.0f%s%5.0f", + map, unicsv_fieldsep, + east, unicsv_fieldsep, + north); + break; + } + case grid_utm: { + int zone; + char zonec; + double north, east; + + if (! GPS_Math_Known_Datum_To_UTM_EN(lat, lon, + &east, &north, &zone, &zonec, unicsv_datum_idx)) + fatal(MYNAME ": Some (or all?) of the coordinates cannot be displayed using \"UTM\".\n"); + gbfprintf(fout, "%02d%s%c%s%.0f%s%.0f", + zone, unicsv_fieldsep, + zonec, unicsv_fieldsep, + east, unicsv_fieldsep, + north); + break; + } + default: + gbfprintf(fout, "%.6f%s%.6f", lat, unicsv_fieldsep, lon); + break; + } + + if FIELD_USED(fld_shortname) unicsv_print_str(shortname); + if FIELD_USED(fld_altitude) { + if (wpt->altitude != unknown_alt) + gbfprintf(fout, "%s%.1f", unicsv_fieldsep, wpt->altitude); + else + gbfputs(unicsv_fieldsep, fout); + } + if FIELD_USED(fld_description) unicsv_print_str(wpt->description); + if FIELD_USED(fld_notes) unicsv_print_str(wpt->notes); + if FIELD_USED(fld_symbol) + unicsv_print_str((wpt->icon_descr != NULL) ? wpt->icon_descr : "Waypoint"); + if FIELD_USED(fld_depth) { + if WAYPT_HAS(wpt, depth) + gbfprintf(fout, "%s%.1f", unicsv_fieldsep, wpt->depth); + else + gbfputs(unicsv_fieldsep, fout); + } + if FIELD_USED(fld_proximity) { + if WAYPT_HAS(wpt, proximity) + gbfprintf(fout, "%s%.f", unicsv_fieldsep, wpt->proximity); + else + gbfputs(unicsv_fieldsep, fout); + } + if FIELD_USED(fld_temperature) { + if WAYPT_HAS(wpt, temperature) + gbfprintf(fout, "%s%.1f", unicsv_fieldsep, wpt->temperature); + else + gbfputs(unicsv_fieldsep, fout); + } + if FIELD_USED(fld_speed) { + if WAYPT_HAS(wpt, speed) + gbfprintf(fout, "%s%.1f", unicsv_fieldsep, wpt->speed); + else + gbfputs(unicsv_fieldsep, fout); + } + if FIELD_USED(fld_course) { + if WAYPT_HAS(wpt, course) + gbfprintf(fout, "%s%.1f", unicsv_fieldsep, wpt->course); + else + gbfputs(unicsv_fieldsep, fout); + } + if FIELD_USED(fld_hdop) { + if (wpt->hdop > 0) + gbfprintf(fout, "%s%.1f", unicsv_fieldsep, wpt->hdop); + else + gbfputs(unicsv_fieldsep, fout); + } + if FIELD_USED(fld_vdop) { + if (wpt->vdop > 0) + gbfprintf(fout, "%s%.1f", unicsv_fieldsep, wpt->vdop); + else + gbfputs(unicsv_fieldsep, fout); + } + if FIELD_USED(fld_pdop) { + if (wpt->pdop > 0) + gbfprintf(fout, "%s%.1f", unicsv_fieldsep, wpt->pdop); + else + gbfputs(unicsv_fieldsep, fout); + } + if FIELD_USED(fld_sat) { + if (wpt->sat > 0) + gbfprintf(fout, "%s%d", unicsv_fieldsep, wpt->sat); + else + gbfputs(unicsv_fieldsep, fout); + } + if FIELD_USED(fld_heartrate) { + if (wpt->heartrate != 0) + gbfprintf(fout, "%s%u", unicsv_fieldsep, wpt->heartrate); + else + gbfputs(unicsv_fieldsep, fout); + } + if FIELD_USED(fld_cadence) { + if (wpt->cadence != 0) + gbfprintf(fout, "%s%u", unicsv_fieldsep, wpt->cadence); + else + gbfputs(unicsv_fieldsep, fout); + } + if FIELD_USED(fld_date) { + if (wpt->creation_time >= SECONDS_PER_DAY) { + struct tm tm; + char buf[32]; + tm = *localtime(&wpt->creation_time); + tm.tm_year += 1900; + tm.tm_mon += 1; + snprintf(buf, sizeof(buf), "%04d/%02d/%02d", tm.tm_year, tm.tm_mon, tm.tm_mday); + gbfprintf(fout, "%s%s", unicsv_fieldsep, buf); + } + else + gbfputs(unicsv_fieldsep, fout); + } + if FIELD_USED(fld_time) { + if (wpt->creation_time != 0) { + struct tm tm; + char buf[32], msec[12]; + tm = *localtime(&wpt->creation_time); + snprintf(buf, sizeof(buf), "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); + if (wpt->microseconds > 0) { + snprintf(msec, sizeof(msec), ".%d", wpt->microseconds / 1000); + strcat(buf, msec); + } + gbfprintf(fout, "%s%s", unicsv_fieldsep, buf); + } + else + gbfputs(unicsv_fieldsep, fout); + } + if FIELD_USED(fld_url) unicsv_print_str(wpt->url); + + gbfputs(UNICSV_LINE_SEP, fout); +} + +/* --------------------------------------------------------------------------- */ + + +static void +unicsv_wr_init(const char *filename) +{ + fout = gbfopen(filename, "w", MYNAME); + + unicsv_outp_flags = 0; + unicsv_grid_idx = grid_unknown; + unicsv_datum_idx = DATUM_WGS84; + unicsv_fieldsep = UNICSV_FIELD_SEP; + unicsv_waypt_ct = 0; + + if (opt_grid != NULL) { + int i; + + if (sscanf(opt_grid, "%d", &i)) { + unicsv_grid_idx = (grid_type) i; + if ((unicsv_grid_idx < GRID_INDEX_MIN) || (unicsv_grid_idx > GRID_INDEX_MAX)) + fatal(MYNAME ": Grid index out of range (%d..%d)!\n", + (int)GRID_INDEX_MIN, (int)GRID_INDEX_MAX); + } + else unicsv_grid_idx = gt_lookup_grid_type(opt_grid, MYNAME); + } + + if (unicsv_grid_idx == grid_bng) + /* force datum to "Ord Srvy Grt Britn" / OSGB36 */ + /* ! ignore parameter "Datum" ! */ + unicsv_datum_idx = DATUM_OSGB36; + else + unicsv_datum_idx = gt_lookup_datum_index(opt_datum, MYNAME); +} + +static void +unicsv_wr_deinit(void) +{ + gbfclose(fout); +} + +static void +unicsv_wr(void) +{ + switch(global_opts.objective) { + case wptdata: + waypt_disp_all(unicsv_waypt_enum_cb); + break; + case trkdata: + track_disp_all(NULL, NULL, unicsv_waypt_enum_cb); + break; + case rtedata: + route_disp_all(NULL, NULL, unicsv_waypt_enum_cb); + break; + case posndata: + fatal(MYNAME ": Realtime positioning not supported.\n"); + } + + gbfprintf(fout, "No%s", unicsv_fieldsep); + + switch(unicsv_grid_idx) { + case grid_bng: + gbfprintf(fout, "BNG-Zone%1$sBNG-East%1$sBNG-North", unicsv_fieldsep); + break; + case grid_utm: + gbfprintf(fout, "UTM-Zone%1$sUTM-Ch%1$sUTM-East%1$sUTM-North", unicsv_fieldsep); + break; + default: + gbfprintf(fout, "Latitude%sLongitude", unicsv_fieldsep); + } + + if FIELD_USED(fld_shortname) gbfprintf(fout, "%sName", unicsv_fieldsep); + if FIELD_USED(fld_altitude) gbfprintf(fout, "%sAltitude", unicsv_fieldsep); + if FIELD_USED(fld_description) gbfprintf(fout, "%sDescription", unicsv_fieldsep); + if FIELD_USED(fld_notes) gbfprintf(fout, "%sNotes", unicsv_fieldsep); + if FIELD_USED(fld_symbol) gbfprintf(fout, "%sSymbol", unicsv_fieldsep); + if FIELD_USED(fld_depth) gbfprintf(fout, "%sDepth", unicsv_fieldsep); + if FIELD_USED(fld_proximity) gbfprintf(fout, "%sProximity", unicsv_fieldsep); + if FIELD_USED(fld_temperature) gbfprintf(fout, "%sTemperature", unicsv_fieldsep); + if FIELD_USED(fld_speed) gbfprintf(fout, "%sSpeed", unicsv_fieldsep); + if FIELD_USED(fld_course) gbfprintf(fout, "%sCourse", unicsv_fieldsep); + if FIELD_USED(fld_hdop) gbfprintf(fout, "%sHDOP", unicsv_fieldsep); + if FIELD_USED(fld_vdop) gbfprintf(fout, "%sVDOP", unicsv_fieldsep); + if FIELD_USED(fld_pdop) gbfprintf(fout, "%sPDOP", unicsv_fieldsep); + if FIELD_USED(fld_sat) gbfprintf(fout, "%sSatellites", unicsv_fieldsep); + if FIELD_USED(fld_heartrate) gbfprintf(fout, "%sHeartrate", unicsv_fieldsep); + if FIELD_USED(fld_cadence) gbfprintf(fout, "%sCadence", unicsv_fieldsep); + if FIELD_USED(fld_date) gbfprintf(fout, "%sDate", unicsv_fieldsep); + if FIELD_USED(fld_time) gbfprintf(fout, "%sTime", unicsv_fieldsep); + if FIELD_USED(fld_url) gbfprintf(fout, "%sURL", unicsv_fieldsep); + + gbfputs(UNICSV_LINE_SEP, fout); + + switch(global_opts.objective) { + case wptdata: + waypt_disp_all(unicsv_waypt_disp_cb); + break; + case trkdata: + track_disp_all(NULL, NULL, unicsv_waypt_disp_cb); + break; + case rtedata: + route_disp_all(NULL, NULL, unicsv_waypt_disp_cb); + break; + default: + break; } } @@ -514,13 +1086,13 @@ unicsv_rd(void) ff_vecs_t unicsv_vecs = { ff_type_file, - { ff_cap_read, 0, 0}, + FF_CAP_RW_ALL, unicsv_rd_init, - NULL, + unicsv_wr_init, unicsv_rd_deinit, - NULL, + unicsv_wr_deinit, unicsv_rd, - NULL, + unicsv_wr, NULL, unicsv_args, CET_CHARSET_ASCII, 0 /* can be changed with -c ... */ -- 2.30.2